package aqua.smile.springboot.rediscache.config;

import aqua.smile.springboot.rediscache.core.CacheProperties;
import aqua.smile.springboot.rediscache.core.CacheTtl;
import aqua.smile.springboot.rediscache.core.MyRedisCacheManager;
import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Configuration
@EnableCaching // 启用缓存
@EnableConfigurationProperties({
        CacheProperties.class
})
@Slf4j
public class RedisAutoConfiguration {

    @Autowired
    private CacheProperties cacheProperties;

    /**
     * 字符序列化器
     *
     * @return 字符序列化器
     */
    @Bean
    public RedisSerializer<String> redisKeySerializer() {
        return RedisSerializer.string();
    }

    /**
     * 对象序列化器 使用jackson.json
     *
     * @return 对象序列化器
     */
    @Bean
    public RedisSerializer<Object> redisValueSerializer() {
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 当str中的属性实体不存在时，忽略
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 指定要序列化的域，field,get和set,以及修饰符范围，ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        // 解决jackson2无法反序列化LocalDateTime的问题
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        om.registerModule(new JavaTimeModule());

        // 携带JavaClass信息
        om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        serializer.setObjectMapper(om);
        return serializer;
    }

    /**
     * 装配 redisTemplate
     *
     * @param factory              连接工厂
     * @param redisKeySerializer   键序列化器
     * @param redisValueSerializer 值序列化器
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory factory,
            RedisSerializer<String> redisKeySerializer,
            RedisSerializer<Object> redisValueSerializer) {

        // RedisConnectionFactory: org.redisson.spring.data.connection.RedissonConnectionFactory
        log.info("RedisConnectionFactory:{}", factory.getClass().getName());

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        redisTemplate.setDefaultSerializer(redisValueSerializer);
        redisTemplate.setHashValueSerializer(redisValueSerializer);

        redisTemplate.setKeySerializer(redisKeySerializer);
        redisTemplate.setHashKeySerializer(redisKeySerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }


    /**
     * redis 缓存管理器
     *
     * @param factory              连接工厂
     * @param redisKeySerializer   键序列化器
     * @param redisValueSerializer 值序列化器
     * @return 缓存管理器
     */
    @Bean(name = "cacheManager")
//    @Primary
    public CacheManager cacheManager(
            RedisConnectionFactory factory,
            RedisSerializer<String> redisKeySerializer,
            RedisSerializer<Object> redisValueSerializer
    ) {
        log.info("装配 RedisCacheManager");

        // 默认配置（强烈建议配置上），比如动态创建出来的都会走此默认配置
        RedisCacheConfiguration dftCfg = getDefConf(redisKeySerializer, redisValueSerializer)
                .entryTtl(Duration.ofHours(3));

        // 针对不同cacheName，设置不同的过期时间
        // ** 用户自定义的缓存配置
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<String, RedisCacheConfiguration>();
        List<CacheTtl> ttls = cacheProperties.getTtls();
        if (CollUtil.isNotEmpty(ttls)) {
            for (CacheTtl ttl : ttls) {
                log.info("加载用户自定义的缓存过期配置，key={}, ttl={}秒", ttl.getName(), ttl.getTtl());
                redisCacheConfigurationMap.put(
                        ttl.getName(),
                        getDefConf(redisKeySerializer, redisValueSerializer).entryTtl(Duration.ofSeconds(ttl.getTtl()))
                );
            }
        }

        return RedisCacheManager.builder(factory)
                .cacheDefaults(dftCfg) // 缺省的配置
                .withInitialCacheConfigurations(redisCacheConfigurationMap) // 个性化处理
                .build();
    }

    /**
     * 装配自定义的 RedisCacheManager
     *
     * @param factory
     * @param redisKeySerializer
     * @param redisValueSerializer
     * @return
     */
    @Bean(name = "myCacheManager")
    @ConditionalOnMissingBean // 这里不会进行装配，会装配上面的 CacheManager
    public CacheManager myCacheManager(
            RedisConnectionFactory factory,
            RedisSerializer<String> redisKeySerializer,
            RedisSerializer<Object> redisValueSerializer
    ) {
        log.info("装配 MyRedisCacheManager");
        RedisCacheConfiguration dftCfg = getDefConf(redisKeySerializer, redisValueSerializer)
                .entryTtl(Duration.ofHours(3));

        MyRedisCacheManager redisCacheManager = new MyRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(factory), dftCfg);
        return redisCacheManager;
    }


    /**
     * redis 缓存key的规则生成器
     *
     * @return key的规则生成器
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, objects) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(":").append(method.getName()).append(":");
            for (Object obj : objects) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }

    private RedisCacheConfiguration getDefConf(RedisSerializer<String> redisKeySerializer, RedisSerializer<Object> redisValueSerializer) {
        return RedisCacheConfiguration.defaultCacheConfig()
                .disableCachingNullValues() // 禁用缓存null值

//                .disableKeyPrefix() // 禁用key的前缀
//                .prefixCacheNameWith("tree:") // 定义前缀，与下一行方法一样的
                .computePrefixWith(cacheName -> "cache".concat(":").concat(cacheName).concat(":"))

                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisKeySerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisValueSerializer));
    }


}
